﻿using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using System.Data.Entity;
using System.Data.Entity.ModelConfiguration;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Eldn.Samples.Ef
{
    class Program
    {
        static void Main(string[] args)
        {
            AppDomain.CurrentDomain.SetData("DataDirectory", Directory.GetCurrentDirectory());
            Database.SetInitializer(new SampleInitializer());

            using (var ctx = new SampleDbContext())
            {
                var name = Console.ReadLine();

                ctx.ProductCatalogs.Add(new ProductCatalog { CatalogName = name });
                ctx.SaveChanges();

                var catalogs = ctx.ProductCatalogs.ToList();

                foreach (var catalog in catalogs)
                {
                    Console.WriteLine("{0}", catalog.CatalogName);
                }
            }

            Console.ReadKey();
        }
    }

    public class SampleDbContext : DbContext
    {
        public DbSet<ProductCatalog> ProductCatalogs { get; set; }

        public DbSet<Customer> Customers { get; set; }

        public DbSet<Product> Products { get; set; }

        public DbSet<Order> Orders { get; set; }

        public DbSet<OrderItem> OrderItems { get; set; }

        public DbSet<SalesPerson> SalesPersons { get; set; }

        public DbSet<SalesPromotion> SalesPromotions { get; set; }

        public DbSet<BankAccount> BankAccounts { get; set; }

        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            modelBuilder.Entity<ProductCatalog>().Property(p => p.ProductCatalogId).HasColumnName("Id");
            modelBuilder.Entity<ProductCatalog>().Property(p => p.CatalogName).HasColumnName("Name");

            modelBuilder.Configurations.Add(new CustomerEntityConfiguration());
            modelBuilder.Configurations.Add(new AddressObjectConfiguration());

            modelBuilder.Entity<Order>().HasMany(o => o.Items).WithRequired(oi => oi.Order).Map(o => o.MapKey("OrderId"));
            modelBuilder.Entity<Order>().HasRequired(o => o.Owner).WithRequiredDependent().Map(m => m.MapKey("OwnerId"));
            modelBuilder.Entity<OrderItem>().HasKey(oi => oi.ItemId).Property(oi => oi.ItemId).HasColumnName("Id");

            modelBuilder.Configurations.Add(new SalesPersonConfiguration());
            modelBuilder.Configurations.Add(new SalesPromotionConfiguration());

            modelBuilder.Entity<BankAccount>().HasKey(a => a.AccountNumber).Property(a => a.AccountNumber).HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);
        }
    }

    public class ProductCatalog
    {
        public int ProductCatalogId { get; set; }

        public string CatalogName { get; set; }

        public string Manufactory { get; set; }

        public decimal ListPrice { get; set; }

        public decimal NetPrice { get; set; }

        public List<Product> ProductsInStock { get; set; }

        public List<SalesPromotion> SalesPromotionHistroy { get; set; }

        public ProductCatalog()
        {
            ProductsInStock = new List<Product>();
            SalesPromotionHistroy = new List<SalesPromotion>();
        }

        public Product GetProductInStock()
        {
            if (ProductsInStock.Count <= 0)
            {
                throw new Exception("No product in stock");
            }

            var product = ProductsInStock[0];
            ProductsInStock.Remove(product);

            return product;
        }

        public void PurchaseProduct(List<Product> products)
        {
            ProductsInStock.AddRange(products);
        }

        public void PurchaseProduct(List<Product> products, decimal newListPrice, decimal newNetPrice)
        {
            PurchaseProduct(products);
            ListPrice = newListPrice;
            NetPrice = newNetPrice;
        }

        public decimal GetPromotionPrice()
        {
            if(SalesPromotionHistroy.Count <= 0)
            {
                return ListPrice;
            }

            var listPrice = 0m;
            var now = DateTime.Now;

            foreach (var promotion in SalesPromotionHistroy)
            {
                if (promotion.StartDate <= now && promotion.EndDate >= now)
                {
                    listPrice = ListPrice * promotion.Discount;
                }
            }

            return listPrice;
        }
    }

    public class BankAccount
    {
        public string AccountNumber { get; set; }

        public string BankName { get; set; }

        public string AccountName { get; set; }

        public DateTime CreatedDate { get; set; }
    }

    public class Customer
    {
        public string IdCardNumber { get; set; }

        public string Name { get; set; }

        public string Gender { get; set; }

        public Address Address { get; set; }

        public string PhoneNumber { get; set; }

        public BankAccount Account { get; set; }
    }

    public class CustomerEntityConfiguration : EntityTypeConfiguration<Customer>
    {
        public CustomerEntityConfiguration()
        {
            HasKey(c => c.IdCardNumber).Property(c => c.IdCardNumber).HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);
            Property(c => c.IdCardNumber).HasMaxLength(20);
            Property(c => c.Name).IsRequired().HasMaxLength(50);

            HasOptional(c => c.Account).WithOptionalDependent().Map(r => r.MapKey("BankAccountNumber"));
        }
    }

    public class Address
    {
        public string Country { get; set; }

        public string Province { get; set; }

        public string City { get; set; }

        public string StreetAddress { get; set; }

        public string ZipCode { get; set; }
    }

    public class AddressObjectConfiguration : ComplexTypeConfiguration<Address>
    {
        public AddressObjectConfiguration()
        {
            Property(a => a.Country).HasColumnName("Country").HasMaxLength(100);
            Property(a => a.Province).HasColumnName("Province").HasMaxLength(100);
            Property(a => a.City).HasColumnName("City").HasMaxLength(100);
            Property(a => a.StreetAddress).HasColumnName("Street");
            Property(a => a.ZipCode).HasColumnName("ZipCode").HasMaxLength(6);
        }
    }

    public class Product
    {
        public int Id { get; set; }

        public ProductCatalog Catalog { get; set; }
    }

    public class Order
    {
        public int OrderId { get; set; }

        public DateTime CreatedTime { get; set; }

        public Customer Owner { get; set; }

        public List<OrderItem> Items { get; set; }

        public SalesPerson CreatedBy { get; set; }

        public SalesPerson ApprovedBy { get; set; }

        public Order()
        {
            Items = new List<OrderItem>();
        }

        public void AddNewItem(Product product, decimal retailPrice)
        {
            var item = new OrderItem
            {
                Products = new List<Product> { product },
                RetailPrice = retailPrice,
                Order = this
            };

            Items.Add(item);
        }

        public bool HasBuy(Product product)
        {
            var hasBuy = false;

            foreach(var item in Items)
            {
                if(item.Products.Count > 0 && item.Products[0].Catalog.ProductCatalogId == product.Catalog.ProductCatalogId)
                {
                    hasBuy = true;
                    break;
                }
            }

            return hasBuy;
        }

        public void MergeItems(Product product, decimal retailPrice, bool mergeIfDifferentPrice)
        {
            foreach(var item in Items)
            {
                if (item.Products.Count > 0 && item.Products[0].Catalog.ProductCatalogId == product.Catalog.ProductCatalogId)
                {
                    if (item.RetailPrice != retailPrice && mergeIfDifferentPrice == false)
                    {
                        throw new Exception("can not merge items because of having different retail prices");
                    }

                    item.RetailPrice = retailPrice;
                    item.Products.Add(product);
                }
            }
        }
    }

    public class OrderItem
    {
        public int ItemId { get; set; }

        public Order Order { get; set; }

        public List<Product> Products { get; set; }

        public decimal RetailPrice { get; set; }

        public OrderItem()
        {
            Products = new List<Product>();
        }
    }

    public class SalesPerson
    {
        public string EmployeeId { get; set; }

        public string Name { get; set; }

        public string Gender { get; set; }

        public DateTime? Birthday { get; set; }

        public DateTime HiredDate { get; set; }

        public List<Order> CreatedOrders { get; set; }

        public List<Order> ApprovedOrders { get; set; }
    }

    public class SalesPersonConfiguration : EntityTypeConfiguration<SalesPerson>
    {
        public SalesPersonConfiguration()
        {
            HasKey(p => p.EmployeeId).Property(p => p.EmployeeId).HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);
            Property(p => p.Name).IsRequired().HasMaxLength(100);
            Property(p => p.Gender).IsRequired().HasMaxLength(1);

            HasMany(p => p.CreatedOrders).WithOptional(o => o.CreatedBy).Map(p => p.MapKey("CreatedBy"));
            HasMany(p => p.ApprovedOrders).WithOptional(o => o.ApprovedBy).Map(p => p.MapKey("ApprovedBy"));
        }
    }

    public class SalesPromotion
    {
        public int Id { get; set; }

        public DateTime StartDate { get; set; }

        public DateTime EndDate { get; set; }

        public decimal Discount { get; set; }

        public List<ProductCatalog> Catalog { get; set; }

        public SalesPromotion()
        {
            Catalog = new List<ProductCatalog>();
        }

        public void AddCatalog(ProductCatalog catalog)
        {
            catalog.SalesPromotionHistroy.Add(this);
            Catalog.Add(catalog);
        }
    }

    public class SalesPromotionConfiguration : EntityTypeConfiguration<SalesPromotion>
    {
        public SalesPromotionConfiguration()
        {
            Property(p => p.Discount).HasPrecision(18, 4);
            HasMany(p => p.Catalog)
                .WithMany(c => c.SalesPromotionHistroy)
                .Map(r =>
                { 
                    r.ToTable("ProductCatalogSalesPromotions");
                    r.MapLeftKey("PromotionId");
                    r.MapRightKey("CatalogId");
                });
        }
    }

    public class SampleInitializer : DropCreateDatabaseIfModelChanges<SampleDbContext>
    {
        protected override void Seed(SampleDbContext ctx)
        {
            var catalog = new ProductCatalog { CatalogName = "Test" };
            ctx.ProductCatalogs.Add(catalog);

            ctx.SaveChanges();
            //base.Seed(context);
        }
    }
}
